/*
 * Exercise 1: The Cost of Trap
 * File name:   osm.c
 *
 *  Created on: Mar 5, 2012
 *      Author: ayash
 *
 *
 * Descriptor:
 * 	implementation of the osm.h
 *
 */

#include <unistd.h> 	// in order to get the host name
#include <sys/time.h> 	// in order to get the current time
#include <stdio.h> 		// for perror
#include <errno.h>
#include <assert.h>

#include "osm.h"


#define DEFAULT_NUM_LOOPS 100000
#define ERROR_VAL -1
#define MAX_MACHINE_NAME_LENGTH  200
#define UNROLLING_FACTOR 128


/*
 * debug prints:
 * DBG_PRINT - for flow prints.
 * ERR_PRINT - should be used when an error occurred
 */
#ifdef NDEBUG

	#define DBG_PRINT(format, args ...)  ((void) 0 ) // do nothing
	#define ERR_PRINT(format, args ...)  ((void) 0 ) // do nothing

#else

	#include <stdio.h>
	#define DBG_PRINT(format, args ...) printf("%s, %d: ",__func__, __LINE__);  \
										printf(format, args)

	#define ERR_PRINT(format, args ...) fprintf(stderr,"%s, %d: ERROR: ",__func__, __LINE__);  \
										fprintf(stderr,format, args)

#endif

// return values
typedef enum
{
	OPERATION_SUCCESS 			= 0,
	ERROR_INVALID_ARGUMENTS		= 1,
	ERROR_FAILED_SYSCALL		= 2

} result_t;

/*
 * keep the machine name after the system return it.
 * Assumption: the user doesn't change the value when he get the pointer
 * to this array
 */
static char machine_name[MAX_MACHINE_NAME_LENGTH] = {'\0'};



/*
 * calculate the offset between before_time & after_time in units of seconds
 */
static result_t calc_offset(const struct timeval * before_time,
							const struct timeval * after_time,
							unsigned int num_interations,
							double *offset_sec)
{
	struct timeval before, after;
	int res_before, res_after;
	double empty_offset;
	unsigned int i;

	assert( NULL != before_time && NULL != after_time && NULL != offset_sec );

	DBG_PRINT("sec= %ld micro = %ld\n",
			  after_time->tv_sec - before_time->tv_sec,
			  after_time->tv_usec - before_time->tv_usec);

	*offset_sec = (after_time->tv_sec - before_time->tv_sec) +
			( (after_time->tv_usec - before_time->tv_usec) * 0.000001 );

	if ( *offset_sec < 0 )
	{
		return ERROR_INVALID_ARGUMENTS;
	}

	// calculate the time it's take for an empty loop
	res_before = gettimeofday( & before , NULL);

	for (i = 0; i < num_interations; i += UNROLLING_FACTOR)
		;

	res_after = gettimeofday( & after , NULL);

	if ( ! res_before && ! res_after )
	{
		// there was no error during getting the time
		DBG_PRINT("empty for: sec= %ld micro = %ld\n",
				   after.tv_sec - before.tv_sec,
				   after.tv_usec - before.tv_usec);

		empty_offset = ( after.tv_sec - before.tv_sec ) +
				( (after.tv_usec - before.tv_usec) * 0.000001 );
		*offset_sec -= empty_offset;
	}

	return OPERATION_SUCCESS;
}

/*
 * calculate the time [s] its take to perform "num_interations" of simple instructions
 */
static result_t calc_instruction_time(unsigned int num_interations,
									  double *instruction_time_sec)
{
	int res_before, res_after;
	unsigned int i;
	register volatile unsigned int tmp;

	struct timeval before_time, after_time;

	assert( 0 == (num_interations % UNROLLING_FACTOR ) );
	assert( NULL != instruction_time_sec );

	res_before = gettimeofday( & before_time , NULL);

	for (i = 0; i < num_interations; i += UNROLLING_FACTOR)
	{
		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;
		tmp = 9; tmp = 10;

		tmp = 1; tmp = 2; tmp = 3; tmp = 4; tmp = 5; tmp = 6; tmp = 7; tmp = 8;

	}

	res_after = gettimeofday( & after_time , NULL);

	if ( 0 != res_before || 0 != res_after  )
	{
		perror("couldn't get current time");

		return ERROR_FAILED_SYSCALL;
	}

	tmp = tmp + 1; //make the compiler happy

	return calc_offset( & before_time, & after_time, num_interations, instruction_time_sec);
}

void dummy_function() { /* empty on purpose */ }

/*
 * calculate the time [s] its take to perform "num_interations" of simple function calls
 */
static result_t calc_function_time(unsigned int num_interations,
								   double *function_time_sec)
{
	int res_before, res_after;
	unsigned int i;
	struct timeval before_time, after_time;

	assert( 0 == (num_interations % UNROLLING_FACTOR ) );
	assert( NULL != function_time_sec );

	res_before = gettimeofday( & before_time , NULL);

	for (i = 0; i < num_interations; i += UNROLLING_FACTOR)
	{
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();


		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function();

		dummy_function(); dummy_function(); dummy_function(); dummy_function();
		dummy_function(); dummy_function(); dummy_function(); dummy_function();

	}

	res_after = gettimeofday( & after_time , NULL);

	if ( 0 != res_before || 0 != res_after )
	{
		perror("couldn't get current time");

		return ERROR_FAILED_SYSCALL;
	}

	return calc_offset( & before_time, & after_time, num_interations, function_time_sec);
}

/*
 * calculate the time [s] its take to perform "num_interations" of simple system call
 */
static result_t calc_trap_time(unsigned int num_interations,
							   double *trap_time_sec)
{
	int res_before, res_after;
	unsigned int i;
	struct timeval before_time, after_time;

	assert( 0 == (num_interations % UNROLLING_FACTOR ) );
	assert ( NULL != trap_time_sec );

	res_before = gettimeofday( & before_time , NULL);

	for (i = 0; i < num_interations; i += UNROLLING_FACTOR)
	{
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;

		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;
		OSM_NULLSYSCALL; OSM_NULLSYSCALL; OSM_NULLSYSCALL;


	}

	res_after = gettimeofday( & after_time , NULL);

	if ( 0 != res_before || 0 != res_after )
	{
		perror("couldn't get current time");

		return ERROR_FAILED_SYSCALL;
	}

	return calc_offset( & before_time, & after_time, num_interations, trap_time_sec);
}

/*
 * calculate the average # of cycles according to the time, CPU frequency and # of iterations
 */
static inline double calc_avg(double time_sec,
							  unsigned long num_cycles_sec,
							  unsigned int num_iterations)
{
	return  ( time_sec * num_cycles_sec ) / num_iterations;
}

/*
 * get the machine name from the system.
 */
static result_t get_machine_name(char **name)
{
	int res;

	assert( NULL != name );

	// we check if we already got the name of the machine so we do it only once
	if ( '\0' == machine_name[0] )
	{
		// get the machine name
		res = gethostname( machine_name, MAX_MACHINE_NAME_LENGTH );
		if ( 0 != res )
		{
			*name = NULL;
			perror("couldn't get machine name: ");
			machine_name[0]  = '\0'; // make sure we will try again next time

			return ERROR_FAILED_SYSCALL;

		}
		else
		{
			*name = machine_name;
			DBG_PRINT("machine name = %s\n", *name);

		}
	}
	else
	{
		// we already have the name of the machine - just assign it
		*name = machine_name;
		DBG_PRINT("machine name = %s\n", *name);

	}

	return OPERATION_SUCCESS;
}


timeMeasurmentStructure measureTimes(int numOfIterations, int cpuMHZ)
{
	timeMeasurmentStructure ret_val;
	result_t result;
	unsigned int real_num_iterations;
	unsigned long num_cycles_sec;
	double instruction_time_sec;
	double function_time_sec;
	double trap_time_sec;

	numOfIterations = (numOfIterations > 0) ? numOfIterations : DEFAULT_NUM_LOOPS;

	// round up the number of iterations
	real_num_iterations = numOfIterations / UNROLLING_FACTOR;
	real_num_iterations +=  ( (numOfIterations % UNROLLING_FACTOR) == 0 )? 0 : 1;
	real_num_iterations *= UNROLLING_FACTOR;

	DBG_PRINT("real_num_iterations = %u\n", real_num_iterations);
	ret_val.numberOfIterations = real_num_iterations;

	// get the machine name
	result = get_machine_name( & ret_val.machineName );

	if ( cpuMHZ <= 0)
	{
		ERR_PRINT("cpuMHZ is non positive! %d\n", cpuMHZ);
	}

	// calculate # of cycles the CPU does in a second
	num_cycles_sec = ((unsigned long)(cpuMHZ)) * 1000000;

	// calculate the average of # cycles it takes for a simple instruction
	result = calc_instruction_time( real_num_iterations, & instruction_time_sec );
	if (OPERATION_SUCCESS != result )
	{
		ret_val.instructionCycles = ERROR_VAL;

		ERR_PRINT("calc_instruction_time() return %d\n", result);
	}
	else
	{
		ret_val.instructionCycles = calc_avg(instruction_time_sec,
											 num_cycles_sec,
											 real_num_iterations);
	}

	// calculate the average of # cycles it takes for a simple function call
	result = calc_function_time( real_num_iterations, & function_time_sec );
	if (OPERATION_SUCCESS != result )
	{
		ret_val.functionCycles = ERROR_VAL;

		ERR_PRINT("calc_function_time() return %d\n", result);
	}
	else
	{
		ret_val.functionCycles = calc_avg(function_time_sec,
										  num_cycles_sec,
										  real_num_iterations);
	}

	// calculate the average of # cycles it takes for a simple system call
	result = calc_trap_time( real_num_iterations, & trap_time_sec );
	if (OPERATION_SUCCESS != result )
	{
		ret_val.trapCycles = ERROR_VAL;

		ERR_PRINT("calc_trap_time() return %d\n", result);
	}
	else
	{
		ret_val.trapCycles = calc_avg(trap_time_sec, num_cycles_sec, real_num_iterations);
	}


	/*
	 * calculate the ratio between the averages - in case on of the values is invalid
	 * the ratio get the ERROR_VAL too
	 */
	if ( ( ERROR_VAL == ret_val.functionCycles ) || ( ERROR_VAL == ret_val.instructionCycles ) )
	{
		ret_val.functionInstructionRatio  = ERROR_VAL;
	}
	else
	{
		ret_val.functionInstructionRatio = ret_val.functionCycles / ret_val.instructionCycles;
	}


	if ( ( ERROR_VAL == ret_val.trapCycles ) || ( ERROR_VAL == ret_val.instructionCycles ) )
	{
		ret_val.trapInstructionRatio  = ERROR_VAL;
	}
	else
	{
		ret_val.trapInstructionRatio = ret_val.trapCycles / ret_val.instructionCycles;
	}



	return ret_val;
}
